/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * Local Includes:
 **/
#include "commonheaders.h"
#include "ctrl.h"
#include "../Include/m_flags.h"

/**
 * Miranda Includes:
 **/

/****************************************************************************
 *	common methods
 ****************************************************************************/

/**
 * name:	ComboBox_Destroy
 * desc:	destroys info structure associated with an edit control
 * param:	hDlg	- owning propertysheetpage dialog
 *			idComboBox	- control identifier for the combobox
 *
 * return	nothing
 **/
VOID ComboBox_Destroy( HWND hDlg, INT idComboBox )
{
	HWND hComboBox = GetDlgItem( hDlg, idComboBox );
	LPCBCTRL pcbCtrl;
	// deleite info structure of combobox
	if( PtrIsValid( pcbCtrl = ( LPCBCTRL )GetUserData( hComboBox ) ) ) {
		if( !( pcbCtrl->wFlags & CBF_INITIALIZED ) ) {
			LPIDSTRLIST pItem = ( LPIDSTRLIST )ComboBox_GetItemData( hComboBox, 0 );
			if( pItem ) {
				FREE( pItem->szText );
				free( pItem );
			}
		}
		free( pcbCtrl );	
	}
}

/**
 * name:	ComboBox_AddItem
 * desc:	Adds a string and associated item data to a combobox.
 *			Item data must be valid!
 * param:	hComboBox	- handle to the combobox's window
 *			pszText		- the text to add to the combobox
 *			lParam		- item data to accociate with the new item
 *
 * return:	zero-based index to new item or CB_ERR on failure
 **/
INT ComboBox_AddItem( HWND hComboBox, LPCTSTR pszText, LPARAM lParam )
{
	if( PtrIsValid( lParam ) ) {
		INT added = ComboBox_AddString( hComboBox, pszText );
		if( SUCCEEDED( added ) ) {
			if( SUCCEEDED( ComboBox_SetItemData( hComboBox, added, lParam ) ) ) 
				return added;
			ComboBox_DeleteString( hComboBox, added );
		}
	}
	return CB_ERR;
}

/**
 * name:	ComboBox_InsertItem
 * desc:	Adds a string and associated item data to a combobox.
 *			Item data must be valid!
 * param:	hComboBox	- handle to the combobox's window
 *			pszText		- the text to add to the combobox
 *			lParam		- item data to accociate with the new item
 *
 * return:	zero-based index to new item or CB_ERR on failure
 **/
INT ComboBox_InsertItem( HWND hComboBox, INT nIndex, LPCTSTR pszText, LPARAM lParam )
{
	if( PtrIsValid( lParam ) ) {
		INT added = ComboBox_InsertString( hComboBox, nIndex, pszText );
		if( SUCCEEDED( added ) ) {
			if( SUCCEEDED( ComboBox_SetItemData( hComboBox, added, lParam ) ) ) 
				return added;
			ComboBox_DeleteString( hComboBox, added );
		}
	}
	return CB_ERR;
}

/**
 * name:	ComboBox_RemoveItem
 * desc:	removes item at nIndex from combobox's listview and returns its item data
 * param:	hComboBox	- handle to the combobox's window
 *			nIndex		- index of item to remove
 *
 * return:	item data of removed item or NULL on failure
 **/
LPARAM ComboBox_RemoveItem( HWND hComboBox, INT nIndex )
{
	LPARAM lParam = NULL;
	if( nIndex >= 0 ) {
		if( !PtrIsValid( lParam = ComboBox_GetItemData( hComboBox, nIndex ) ) )
			lParam = NULL;
		if( FAILED( ComboBox_DeleteString( hComboBox, nIndex ) ) )
			lParam = NULL;
	}
	return lParam;
}

/**
 * name:	ComboBox_SelChanged
 * desc:	handle selchange notification of static comboboxes like country lists
 * param:	hDlg	  - window handle of propertysheetpage
 *			hComboBox - handle of combobox
 *
 * return:	nothing
 **/
VOID ComboBox_SelChanged( HWND hDlg, HWND hComboBox )
{
	LPCBCTRL pcbCtrl = ( LPCBCTRL )GetUserData( hComboBox );
	INT nCurSel = ComboBox_GetCurSel( hComboBox );

	if( PtrIsValid( pcbCtrl ) && !( pcbCtrl->wFlags & CTRLF_CHANGED ) &&	nCurSel >= 0 && pcbCtrl->nTopItem != nCurSel ) {
		pcbCtrl->wFlags |= CTRLF_CHANGED;
		SendMessage( GetParent( hDlg ), PSM_CHANGED, 0, 0 );
	}
}

/**
 * name:	ComboBox_CountrySelChanged
 * desc:	handle selchange notification of static comboboxes like country lists
 * param:	hDlg	  - window handle of propertysheetpage
 *			hComboBox - handle of combobox
 *
 * return:	nothing
 **/
VOID ComboBox_CountrySelChanged( HWND hDlg, HWND hComboBox, HWND hIconCtrl )
{
	INT nCurSel = ComboBox_GetCurSel( hComboBox );

	if( nCurSel >= 0 ) {
		if( hIconCtrl && ServiceExists( MS_FLAGS_LOADFLAGICON ) ) {
			IDSTRLIST *pCountry = ( IDSTRLIST * )ComboBox_GetItemData( hComboBox, nCurSel );
			if( pCountry ) {
				HICON hIco = ( HICON )CallService( MS_FLAGS_LOADFLAGICON, pCountry->nID, 0 );

				if( hIco ) {
					SendMessage( hIconCtrl, STM_SETIMAGE, IMAGE_ICON, ( LPARAM )hIco );
				}
			}
		}

		if( !PspIsLocked( hDlg ) ) {
			LPCBCTRL pcbCtrl = ( LPCBCTRL )GetUserData( hComboBox );

			if( PtrIsValid( pcbCtrl ) && !( pcbCtrl->wFlags & CTRLF_CHANGED ) && pcbCtrl->nTopItem != nCurSel ) {
				pcbCtrl->wFlags |= CTRLF_CHANGED;
				SendMessage( GetParent( hDlg ), PSM_CHANGED, 0, 0 );
			}
		}
	}
}

/**
 * name:	ComboBox_GetItem
 * desc:	read item text and data and return both
 * param:	hComboBox	- handle to combobox
 *			nIndex		- index to item whose data to return
 *			szText		- address of string holding the text
 *			ccText		- max number of chars szText can hold without reallocating
 *			lParam		- pointer retrieving address of data structure
 **/
LPVOID ComboBox_GetItem( HWND hComboBox, INT nIndex, LPTSTR *szText, LPINT ccText, LPARAM *lParam )
{
	// retrieve item data
	if( lParam ) {
		*lParam = ( LPARAM )ComboBox_GetItemData( hComboBox, nIndex );
	}

	// retrieve item text
	if( szText && ccText ) {
		INT ccItem = ComboBox_GetLBTextLen( hComboBox, nIndex ) + 6;	// space for text + " SMS" eventually
		
		if( ccItem < *ccText || PtrIsValid( *szText = ( LPTSTR )realloc( *szText, ( ccItem + 1 ) * sizeof( TCHAR ) ) ) ) {
			*ccText = max( ccItem, *ccText );
			if( FAILED( ComboBox_GetLBText( hComboBox, nIndex, *szText ) ) )
				return NULL;
		}
	}
	return ( szText ? ( LPVOID )*szText : ( LPVOID )*lParam );
}

/****************************************************************************
 *	static combobox methods
 ****************************************************************************/

/**
 * name:	ComboBox_AddItems
 * desc:	adds a IdStrList to the combobox. Only way to interact with this combobox is by selecting an item.
 *			Deleting or anything else is not allowed. Be careful to call ComboBox_RemoveItem
 *			or ComboBox_DeleteString but NEVER ComboBox_DeleteItem to delete an item added with this function!!
 * param:	hComboBox		- handle to the combobox's window
 *			pList			- list to add
 *			numItems		- number of items in idList
 *			nUnspecifiedID	- id an item must have to be the "unspecified item"
 *			nSelectedID		- if pszSelected is NULL, this is the id of the item to select
 *			pszSelected		- text of the item to mark as selected
 *
 * return:	true or false
 **/
BOOLEAN ComboBox_AddItems(
			HWND hComboBox,
			LPIDSTRLIST pList,
			UINT numItems,
			INT nUnspecifiedID,
			INT nSelectedID,
			LPCTSTR pszSelected )
{
	INT	add;
	LPIDSTRLIST pUnspec = NULL;
	BOOLEAN bItemIsSelected = FALSE;

	while( PtrIsValid( pList ) && numItems-- > 0 ) {
		// remember item with id indicating "unspecified item", to add at the end
		if( !pUnspec && ( nUnspecifiedID == pList->nID ) ) {
			pUnspec = pList;
		}
		else {
			if( FAILED( add = ComboBox_AddItem( hComboBox, pList->ptszTranslated, ( LPARAM )pList ) ) ) return FALSE;
			if( ( !pszSelected && pList->nID == nSelectedID ) || ( pszSelected && ( !_tcscmp( pszSelected, pList->szText ) || ( pList->ptszTranslated != pList->szText && !_tcscmp( pszSelected, pList->ptszTranslated ) ) ) ) ) {
				ComboBox_SetCurSel( hComboBox, add );
				bItemIsSelected = TRUE;
			}
		}
		pList++;
	}
	// add unspecified item
	if( pUnspec ) {
		if( FAILED( add = ComboBox_InsertItem( hComboBox, 0, pUnspec->ptszTranslated, ( LPARAM )pUnspec ) ) ) return FALSE;
		if( !bItemIsSelected ) ComboBox_SetCurSel( hComboBox, add );
	}
	return TRUE;
}

/**
 * name:	ComboBox_DropDown
 * desc:	adds the combobox items if not jet done on dropdown message
 * param:	hComboBox - handle to the dropdown control
 *
 * return:	nothing
 **/
VOID ComboBox_DropDown( HWND hComboBox )
{
	LPCBCTRL pcbCtrl;

	if( PtrIsValid( pcbCtrl = ( LPCBCTRL )GetUserData( hComboBox ) ) && !( pcbCtrl->wFlags & CBF_INITIALIZED ) ) {
		if( ComboBox_GetCount( hComboBox ) > 1 ) {
			pcbCtrl->wFlags |= CBF_INITIALIZED;
		}
		else {
			LPIDSTRLIST pItem = ( LPIDSTRLIST )ComboBox_RemoveItem( hComboBox, 0 );
			if( PtrIsValid( pItem ) ) {
				ComboBox_AddItems( hComboBox, pcbCtrl->pItems, pcbCtrl->nItems, pcbCtrl->pItems[0].nID, pItem->nID, ( pcbCtrl->nType == DBVT_TCHAR ) ? pItem->szText : NULL );
				FREE( pItem->szText );
				free( pItem );
				pcbCtrl->wFlags |= CBF_INITIALIZED;
				pcbCtrl->nTopItem = ComboBox_GetCurSel( hComboBox );
			}
		}
	}
}

static INT ComboBox_FindItemID( HWND hComboBox, INT nIndexStart, INT nID )
{
	LPIDSTRLIST pItem;

	while( PtrIsValid( pItem = ( LPIDSTRLIST )ComboBox_GetItemData( hComboBox, nIndexStart ) ) ) {
		if( pItem->nID == nID )
			return nIndexStart;
		nIndexStart++;
	}
	return CB_ERR;
}

/**
 * name:	ComboBox_SelectItem
 * desc:	select one of the static items of a combobox by a setting of the database
 * param:	hDlg		- parent propertysheetpage dialogbox handle
 *			idCombo		- control id of the combobox whose item to select
 *			hContact	- contacht handle to read the settings of
 *			szModule	- default module to look in for the setting
 *			szBaseProto - basic protocol module of the contact to search the setting in, too
 *			szSetting	- the desired setting to read
 *			errorValue	- item to select if no setting was found anywhere
 *			isSigned	- whether the db value is signed or not
 *
 * return:	status of combobox, whether it is changed or not
 **/
BYTE ComboBox_SelectItem(
					HWND hDlg,
					INT idCombo,
					LPIDSTRLIST pItems,
					UINT nItems,
					HANDLE hContact,
					LPCSTR szModule,
					LPCSTR szBaseProto,
					LPCSTR szSetting,
					INT errorValue,
					BOOLEAN isSigned )
{
	HWND		hComboBox = GetDlgItem( hDlg, idCombo );
	LPCBCTRL	pcbCtrl;
	UINT		i;
	
	// allocate combobox's infostructure
	if( !PtrIsValid( pcbCtrl = ( LPCBCTRL )GetUserData( hComboBox ) ) ) {
		if( !( pcbCtrl = ( LPCBCTRL )malloc( sizeof( CBCTRL ) ) ) )
			return CB_ERR;
		pcbCtrl->nType = CTRL_COMBOBOX_STAT;
		pcbCtrl->wFlags = 0;
		pcbCtrl->nTopItem = -1;
		pcbCtrl->pItems = pItems;
		pcbCtrl->nItems = nItems;

		SetUserData( hComboBox, pcbCtrl );
	}
	// if combobox has not changed yet, readout new settings from db
	if( !( pcbCtrl->wFlags & CTRLF_CHANGED ) ) {
		DBVARIANT dbv;
		LPIDSTRLIST pItem = NULL;

		LPCSTR szSubMod;
		if( !szModule ) szSubMod = USERINFO;
		else szSubMod = szModule;

		// read setting from contact's basic protocol
		pcbCtrl->wFlags &= ~( CTRLF_HASCUSTOM|CTRLF_HASPROTO|CTRLF_HASMETA );
		pcbCtrl->wFlags |= DB::Setting::GetTStringCtrl( hContact, szModule, szSubMod, szBaseProto, szSetting, &dbv );
		pcbCtrl->nType = dbv.type;
		// enable or disable combobox according to the usersettings
		EnableWindow( hComboBox, 
			!hContact ||
			( pcbCtrl->wFlags & CTRLF_HASCUSTOM ) || 
			!( pcbCtrl->wFlags & ( CTRLF_HASPROTO|CTRLF_HASMETA ) ) ||
			!DB::Setting::GetByte( SET_PROPSHEET_PCBIREADONLY, 0 ) );
		// combobox dropped down before
		if( ( pcbCtrl->wFlags & CBF_INITIALIZED ) ) {
			INT	iVal = CB_ERR;
			
			switch( dbv.type ) {
				case DBVT_BYTE:
					iVal = ComboBox_FindItemID( hComboBox, 0, ( INT ) ( isSigned ? dbv.cVal : dbv.bVal ) );
					break;
				case DBVT_WORD:
					iVal = ComboBox_FindItemID( hComboBox, 0, ( INT )( isSigned ? dbv.sVal : dbv.wVal ) );
					break;
				case DBVT_TCHAR:
					iVal = SendMessage( hComboBox, CB_FINDSTRINGEXACT, 0, ( LPARAM )TranslateTS( dbv.ptszVal ) );
					break;
			}
			pcbCtrl->nTopItem = iVal >= 0 ? iVal : errorValue;
		}
		// this is the first time, data is set for combobox
		else
		if( pcbCtrl->nTopItem == -1 && ( pItem = ( LPIDSTRLIST )malloc( sizeof( IDSTRLIST ) ) ) ) {
			ZeroMemory( pItem, sizeof( IDSTRLIST ) );
			switch( dbv.type ) {
				case DBVT_BYTE:
					pItem->nID = ( INT )( isSigned ? dbv.cVal : dbv.bVal );
					for( i = 0; i < nItems; i++ ) {
						if( pItems[i].nID == pItem->nID ) {
							pItem->szText = _tcsdup( pItems[i].szText );
							pItem->ptszTranslated = pItems[i].ptszTranslated ? pItems[i].ptszTranslated : TranslateTS( pItem->szText );
							break;
   						}
					}
					if( i == nItems )	{
						pItem->nID = pItems[0].nID;
						pItem->szText = _tcsdup( pItems[0].szText );
						pItem->ptszTranslated = pItems[0].ptszTranslated ? pItems[0].ptszTranslated : TranslateTS( pItem->szText );
					}						
					break;
				case DBVT_WORD:
					pItem->nID = ( INT )( isSigned ? dbv.sVal : dbv.wVal );
					for( i = 0; i < nItems; i++ )
						if( pItems[i].nID == pItem->nID ) {
							pItem->szText = _tcsdup( pItems[i].szText );
							pItem->ptszTranslated = pItems[i].ptszTranslated ? pItems[i].ptszTranslated : TranslateTS( pItem->szText );
							break;
   						}
					if( i == nItems )	{
						pItem->nID = pItems[0].nID;
						pItem->szText = _tcsdup( pItems[0].szText );
						pItem->ptszTranslated = pItems[0].ptszTranslated ? pItems[0].ptszTranslated : TranslateTS( pItem->szText );
					}						
					break;
				case DBVT_TCHAR:
				{
					LPTSTR ptszVal = TranslateTS( dbv.ptszVal );

					for( i = 0; i < nItems; i++ ) {
						if( !_tcscmp( pItems[i].ptszTranslated, ptszVal ) ) {
							pItem->szText = _tcsdup( pItems[i].szText );
							pItem->ptszTranslated = TranslateTS( pItem->szText );
							break;
						}
					}
					if( i < nItems )
						break;
				}
				// first of each list is unspecified item
				default:
					pItem->nID = pItems->nID;
					pItem->szText = _tcsdup( pItems->szText );
					pItem->ptszTranslated = TranslateTS( pItem->szText );
					break;
			}

			pcbCtrl->nTopItem = ComboBox_AddItem( hComboBox, pItem->ptszTranslated, ( LPARAM )pItem );
		}
		ComboBox_SetCurSel( hComboBox, pcbCtrl->nTopItem );
		pcbCtrl->wFlags &= ~CTRLF_CHANGED; 
		DB::Variant::Free( &dbv );
	}
	return ( pcbCtrl->wFlags & CTRLF_CHANGED );
}

/**
 * name:	ComboBox_WriteItem
 * desc:	writes the selected item to the database
 * param:	hDlg	  - owning dialogbox
 *			idCombo	  - control identifier for the combobox
 *			hContact  - handle to the contact the setting is to be written to
 *			szModule  - contacts module to write the setting in
 *			szSetting - setting to save
 *			bType	  - datatype the setting is to be written as
 *
 * return:	the datatype the setting was written as or DBVT_DELETED
 **/
BYTE ComboBox_WriteItem( HWND hDlg, INT idCombo, HANDLE hContact, LPCSTR szModule, LPCSTR szBaseProto, LPCSTR szSetting, BYTE bType, BOOLEAN isSigned )
{
	HWND hComboBox = GetDlgItem( hDlg, idCombo );
	LPCBCTRL pcbCtrl;
	INT nCurSel;
	LPIDSTRLIST pItem;

	if( hComboBox && PtrIsValid( pcbCtrl = ( LPCBCTRL )GetUserData( hComboBox ) ) && ( pcbCtrl->wFlags & CTRLF_CHANGED ) ) {
		// first item specifies undefined item, so delete setting
		if( 0 < ( nCurSel = ComboBox_GetCurSel( hComboBox ) ) && PtrIsValid( pItem = ( LPIDSTRLIST )ComboBox_GetItemData( hComboBox, nCurSel ) ) ) {
			// save settings to base protocol if contact handle identifies owner!
			if( !hContact ) szModule = szBaseProto;
			switch( bType ) {
				case DBVT_BYTE:
					if( DB::Setting::WriteByte( hContact, szModule, szSetting, ( BYTE )pItem->nID ) ) return DBVT_DELETED;
					if( hContact ) pcbCtrl->wFlags |= CTRLF_HASCUSTOM;
					break;
				case DBVT_WORD:
					if( DB::Setting::WriteWord( hContact, szModule, szSetting, ( WORD )pItem->nID ) ) return DBVT_DELETED;
					if( hContact ) pcbCtrl->wFlags |= CTRLF_HASCUSTOM;
					break;
				case DBVT_ASCIIZ:
				{
					CHAR *ptszText =	mir_t2a(pItem->szText);
#ifdef _UNICODE
					if( !ptszText && DB::Setting::WriteWString( hContact, szModule, szSetting, ( LPWSTR )pItem->szText ) ) return DBVT_DELETED;
					else
#endif
					if( DB::Setting::WriteAString( hContact, szModule, szSetting, ptszText ) ) return DBVT_DELETED;
					if( hContact ) pcbCtrl->wFlags |= CTRLF_HASCUSTOM;
					break;
				}
				case DBVT_WCHAR:
					if( DB::Setting::WriteTString( hContact, szModule, szSetting, ( LPTSTR )pItem->szText ) ) return DBVT_DELETED;
					if( hContact ) pcbCtrl->wFlags |= CTRLF_HASCUSTOM;
					break;
			}
		}
		else {
			DB::Setting::Delete( hContact, szModule, szSetting );
			bType = DBVT_DELETED;
			pcbCtrl->wFlags &= ~CTRLF_HASCUSTOM;
			if( pcbCtrl->wFlags & CTRLF_HASPROTO ) {
				pcbCtrl->wFlags &= ~CTRLF_CHANGED;
				ComboBox_SelectItem( hDlg, idCombo, NULL, NULL, hContact, NULL, szBaseProto, szSetting, 0, isSigned );
			}
		}
		pcbCtrl->nTopItem = ComboBox_GetCurSel( hComboBox );
		pcbCtrl->wFlags &= ~CTRLF_CHANGED;
		InvalidateRect( hComboBox, NULL, TRUE );
	}
	return bType;
}
